1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.assetmanager; 12 import hip.util.concurrency; 13 import hip.util.data_structures: Node; 14 import hip.util.reflection; 15 import hip.error.handler; 16 import hip.console.log : hiplog; 17 18 19 version(WebAssembly) version = CustomRuntime; 20 version(CustomRuntimeTest) version = CustomRuntime; 21 version(PSVita) version = CustomRuntime; 22 23 private string buildConstantsFromFolderTree(string code, Node!string node, int depth = 0) 24 { 25 import hip.util.path; 26 import hip.util.string; 27 if(node.hasChildren && node.data.extension == "") 28 { 29 code = "\t".repeat(depth)~"class " ~ node.data~ "\n"~"\t".repeat(depth)~"{\n"; 30 foreach(child; node.children) 31 { 32 code~= "\t".repeat(depth)~buildConstantsFromFolderTree(code, child, depth+1)~"\n"; 33 } 34 code~="\n"~"\t".repeat(depth)~"}\n"; 35 } 36 else if(!node.hasChildren && node.data.extension != "") 37 { 38 string propName = node.data[0..$-(node.data.extension.length+1)]; 39 return "\tpublic static enum "~propName~" = `"~node.buildPath~"`;"; 40 } 41 return code; 42 } 43 44 mixin template HipAssetsGenerateEnum(string filePath) 45 { 46 import hip.util.path; 47 mixin(buildConstantsFromFolderTree("", buildFolderTree(import(filePath).split('\n')))); 48 } 49 50 51 import hip.util.system; 52 import hip.util.concurrency; 53 public import hip.asset; 54 public import hip.assets.image; 55 public import hip.assets.audioclip; 56 public import hip.assets.texture; 57 public import hip.assets.tilemap; 58 public import hip.assets.font; 59 public import hip.assets.csv; 60 public import hip.assets.jsonc; 61 public import hip.assets.ini; 62 public import hip.api.data.commons; 63 public import hip.assets.textureatlas; 64 public import hip.util.data_structures; 65 66 67 68 final class HipAssetLoadTask : IHipAssetLoadTask 69 { 70 string name; 71 string path; 72 HipAssetResult _result = HipAssetResult.cantLoad; 73 HipAsset _asset = null; 74 protected HipWorkerThread worker; 75 protected void[] partialData; 76 77 78 private string fileRequesting; 79 private size_t lineRequesting; 80 81 this(string path, string name, HipAsset asset, string fileRequesting, size_t lineRequesting) 82 { 83 assert(name != null, "Asset load task can't receive null name"); 84 this.path = path; 85 this.name = name; 86 this._asset = asset; 87 this.fileRequesting = fileRequesting; 88 this.lineRequesting = lineRequesting; 89 if(asset is null) 90 _result = HipAssetResult.cantLoad; 91 else 92 _result = HipAssetResult.loaded; 93 } 94 95 bool hasFinishedLoading() const{return result == HipAssetResult.loaded;} 96 bool opCast(T : bool)() const{return hasFinishedLoading;} 97 98 99 void addOnCompleteHandler(void delegate(IHipAsset) onComplete) 100 { 101 HipAssetManager.addOnCompleteHandler(this, onComplete); 102 } 103 void addOnCompleteHandler(void delegate(string) onComplete) 104 { 105 HipAssetManager.addOnCompleteHandler(this, (asset) 106 { 107 HipFileAsset theAsset = cast(HipFileAsset)asset; 108 assert(theAsset !is null, "Asset received is not a text"); 109 onComplete(theAsset.getText); 110 }); 111 } 112 113 114 void into(string*[] variables...) 115 { 116 import hip.error.handler; 117 final switch(_result) with(HipAssetResult) 118 { 119 case loaded: 120 foreach(v; variables) 121 *v = (cast(HipFileAsset)(asset)).getText; 122 break; 123 case loading: 124 //variables are implicitly `scope`, need to duplicate. 125 string*[] vars = variables.dup; 126 addOnCompleteHandler((string data) 127 { 128 foreach(v; vars) 129 *v = data; 130 }); 131 break; 132 case cantLoad: 133 ErrorHandler.showWarningMessage("Can't load a null asset into a variable address", name); 134 break; 135 } 136 } 137 138 139 void into(void* function(IHipAsset asset) castFunc, IHipAsset*[] variables...) 140 { 141 import hip.error.handler; 142 final switch(_result) with(HipAssetResult) 143 { 144 case loaded: 145 foreach(v; variables) 146 *v = cast(IHipAsset)castFunc(asset); 147 break; 148 case loading: 149 //variables are implicitly `scope`, need to duplicate. 150 IHipAsset*[] vars = variables.dup; 151 addOnCompleteHandler((IHipAsset completeAsset) 152 { 153 IHipAsset theAsset = cast(IHipAsset)castFunc(completeAsset); 154 assert(theAsset !is null, "Null asset received in complete handler?"); 155 foreach(v; vars) 156 *v = theAsset; 157 }); 158 break; 159 case cantLoad: 160 ErrorHandler.showWarningMessage("Can't load a null asset into a variable address", name); 161 break; 162 } 163 } 164 165 void await() 166 { 167 if(_result == HipAssetResult.loading) 168 HipAssetManager.awaitTask(this); 169 } 170 171 void givePartialData(void[] data) 172 { 173 import hip.util.conv:to; 174 if(partialData !is null) 175 { 176 version(CustomRuntime) 177 assert(false, "AssetLoadTask already has partial data for task "~name~" (requested at "~fileRequesting~":"~lineRequesting.to!string~")"); 178 else 179 throw new Error("AssetLoadTask already has partial data for task "~name~" (requested at "~fileRequesting~":"~lineRequesting.to!string~")"); 180 } 181 partialData = data; 182 } 183 184 void[] takePartialData() 185 { 186 import hip.util.conv:to; 187 if(partialData is null) 188 { 189 version(CustomRuntime) 190 assert(false, "No partial data was set before taking it for task "~name~ " (requested at "~fileRequesting~":"~lineRequesting.to!string~")"); 191 else 192 throw new Error("No partial data was set before taking it for task "~name~ " (requested at "~fileRequesting~":"~lineRequesting.to!string~")"); 193 } 194 void[] ret = partialData; 195 partialData = null; 196 return ret; 197 } 198 199 HipAssetResult result() const {return _result;} 200 IHipAsset asset(){return _asset;} 201 HipAssetResult result(HipAssetResult newResult){return _result = newResult;} 202 IHipAsset asset(IHipAsset newAsset){return _asset = cast(HipAsset)newAsset;} 203 204 } 205 206 207 208 import hip.api.data.font; 209 210 211 212 mixin template HipDeferredLoadImpl() 213 { 214 import hip.util.reflection; 215 216 private void deferredLoad(T, string funcName)(IHipAssetLoadTask task) 217 { 218 alias func = __traits(getMember, typeof(this), funcName); 219 if(task.asset !is null) 220 func( cast(T)task.asset); 221 else 222 HipAssetManager.addOnCompleteHandler(task, (asset) 223 { 224 func(cast(T)asset); 225 }); 226 } 227 228 pragma(msg, typeof(this).stringof, hasType!"hip.assets.texture.HipTexture", hasMethod!(typeof(this), "setTexture", IHipTexture)); 229 static if(hasType!"hip.assets.texture.HipTexture" && hasMethod!(typeof(this), "setTexture", IHipTexture)) 230 { 231 final void setTexture(IHipAssetLoadTask task) 232 { 233 deferredLoad!(HipTexture, "setTexture")(task); 234 } 235 } 236 static if(hasType!"hip.api.data.font.IHipFont" && hasMethod!(typeof(this), "setFont", IHipFont)) 237 { 238 final void setFont(IHipAssetLoadTask task) 239 { 240 deferredLoad!(HipFontAsset, "setFont")(task); 241 } 242 } 243 } 244 245 string HipDeferredLoad() 246 { 247 return q{ 248 mixin HipDeferredLoadImpl __dload__; 249 static foreach(func; __traits(allMembers, __dload__)) 250 { 251 mixin("alias ",func," = __dload__.", func,";"); 252 }}; 253 } 254 255 256 257 class HipAssetManager 258 { 259 import hip.config.opts; 260 261 protected __gshared HipWorkerPool workerPool; 262 __gshared float currentTime; 263 //Caching 264 protected __gshared HipAsset[string] assets; 265 protected __gshared HipAssetLoadTask[string] loadQueue; 266 267 //Thread Communication 268 protected __gshared HipAssetLoadTask[] completeQueue; 269 protected __gshared DebugMutex completeMutex; 270 protected __gshared void delegate(IHipAsset)[][HipAssetLoadTask] completeHandlers; 271 272 273 274 public static void initialize() 275 { 276 completeMutex = new DebugMutex(); 277 workerPool = new HipWorkerPool(HIP_ASSETMANAGER_WORKER_POOL); 278 } 279 280 version(HipConcurrency) 281 { 282 import core.sync.mutex; 283 import std.compiler; 284 static bool isAsync = true; 285 } 286 else 287 { 288 static bool isAsync = false; 289 } 290 291 292 @ExportD static IHipAsset getAsset(string name) 293 { 294 if(HipAsset* asset = name in assets) 295 return *asset; 296 return null; 297 } 298 299 @ExportD static string getStringAsset(string name) 300 { 301 IHipAsset asset = getAsset(name); 302 if(asset !is null) 303 { 304 HipFileAsset fA = cast(HipFileAsset)asset; 305 assert(fA !is null, "Asset fetched is not a file asset."); 306 return fA.getText; 307 } 308 else 309 return null; 310 } 311 312 static pragma(inline, true) T get(T)(string name) {return cast(T)getAsset(name);} 313 static pragma(inline, true) T get(T : string)(string name) {return getStringAsset(name);} 314 315 ///Returns whether asset manager is loading anything 316 @ExportD static bool isLoading(){return !workerPool.isIdle;} 317 ///Stops the code from running and awaits asset manager to finish loading 318 @ExportD static void awaitLoad() 319 { 320 workerPool.await; 321 update(); 322 } 323 324 static void awaitTask(HipAssetLoadTask task) 325 { 326 version(HipConcurrency) 327 { 328 import core.sync.semaphore; 329 auto semaphore = new Semaphore(0); 330 task.worker.pushTask("Await Single", () 331 { 332 semaphore.notify; 333 }); 334 semaphore.wait; 335 destroy(semaphore); 336 update(); 337 } 338 } 339 340 private static HipWorkerThread loadWorker(string taskName, void delegate() loadFn, void delegate(string taskName) onFinish = null, bool onMainThread = false) 341 { 342 //TODO: Make it don't use at all worker and threads. 343 //? Maybe it is not actually needed, as it can be handled by version(HipConcurrency) 344 return workerPool.pushTask(taskName, loadFn, onFinish, onMainThread); 345 // if(isAsync) 346 // return workerPool.pushTask(taskName, loadFn, onFinish, onMainThread); 347 // else 348 // { 349 // loadFn(); 350 // if(onFinish !is null) 351 // onFinish(taskName); 352 // } 353 // return null; 354 } 355 356 /** 357 * Checks whether the file has been loaded already or not: 358 * if: returns its previous task 359 * else: Put a new one on load cache and retunr 360 */ 361 private static HipAssetLoadTask loadBase(string taskName, string path, lazy HipWorkerThread worker, string fileRequesting = __FILE__, size_t lineRequesting = __LINE__) 362 { 363 HipAsset asset = cast(HipAsset)getAsset(path); 364 if(asset !is null){return new HipAssetLoadTask(path, taskName, asset, fileRequesting, lineRequesting);} 365 else if(HipAssetLoadTask* task = path in loadQueue){return *task;} 366 367 auto task = new HipAssetLoadTask(path, taskName, null, fileRequesting, lineRequesting); 368 loadQueue[path] = task; 369 task.result = HipAssetResult.loading; 370 task.worker = worker; 371 return task; 372 } 373 374 private static void delegate(HipAsset) onSuccessLoad(HipAssetLoadTask task) 375 { 376 return (HipAsset asset) 377 { 378 ///Will need specific code. Web works differently 379 version(WebAssembly) 380 { 381 workerPool.signalTaskFinish(); 382 } 383 task.asset = asset; 384 task.result = HipAssetResult.loaded; 385 putComplete(task); 386 }; 387 } 388 389 private static void delegate(string err = "") onFailureLoad(HipAssetLoadTask task) 390 { 391 return (err) 392 { 393 ErrorHandler.showWarningMessage("Could not load task: "~ task.name, err); 394 task.result = HipAssetResult.cantLoad; 395 putComplete(task); 396 }; 397 } 398 399 400 /** 401 * loadSimple must be used when the asset can be totally constructed on the worker thread and then returned to the main thread 402 */ 403 private static HipAssetLoadTask loadSimple(string taskName, string path, void delegate(string pathOrLocation, 404 void delegate(HipAsset) onSuccess, void delegate(string err = "") onFailure) loadAsset, 405 string f = __FILE__, size_t l = __LINE__) 406 { 407 HipAssetLoadTask task; 408 taskName = taskName~":"~path; 409 task = loadBase(taskName, path, loadWorker(taskName, () 410 { 411 loadAsset(path, onSuccessLoad(task), onFailureLoad(task)); 412 }), f, l); 413 return task; 414 } 415 416 version(WebAssembly) 417 { 418 419 private static void delegate(void[] partialData) onSuccessLoadFirstStep(HipAssetLoadTask task, 420 void delegate(string taskName) nextStep) 421 { 422 return (void[] partialData) 423 { 424 task.givePartialData(partialData); 425 workerPool.notifyOnFinishOnMainThread(nextStep, false)(task.name); 426 }; 427 } 428 429 /** 430 * The main difference in that version is that it doesn't depends on HipConcurrency to put 431 * on notifyOnFinish. That was decided because it is impossible to actually know when something 432 * has finished on Browser. The notfyOnFinish callback must be passed manually. 433 */ 434 private static HipAssetLoadTask loadComplex( 435 string taskName, 436 string path, 437 void delegate( 438 string pathOrLocation, 439 void delegate(void[] partialData) onFirstStepComplete, 440 void delegate(string err = "") onFailure 441 ) loadAsset, 442 443 void delegate ( 444 void[] partialData, 445 void delegate(HipAsset) onSuccess, 446 ) mainThreadLoadFunction, 447 string f = __FILE__, 448 size_t l = __LINE__ 449 ) 450 { 451 HipAssetLoadTask task; 452 taskName = taskName~":"~path; 453 454 auto nextStep = (string _) 455 { 456 mainThreadLoadFunction(task.takePartialData(), onSuccessLoad(task)); 457 }; 458 459 task = loadBase(taskName, path, loadWorker(taskName, () 460 { 461 loadAsset(path, onSuccessLoadFirstStep(task, nextStep), onFailureLoad(task)); 462 }, null, true), f, l); 463 464 return task; 465 } 466 } 467 else 468 { 469 /** 470 * loadComplex is used when part of the asset can be constructed on worker thread, but for completing the load, it must finish on main thread 471 */ 472 private static HipAssetLoadTask loadComplex( 473 string taskName, 474 string path, 475 void delegate( 476 string pathOrLocation, 477 void delegate(void[] partialData) onFirstStepComplete, 478 void delegate(string err = "") onFailure 479 ) loadAsset, 480 481 void delegate ( 482 void[] partialData, 483 void delegate(HipAsset) onSuccess, 484 ) mainThreadLoadFunction, 485 string f = __FILE__, 486 size_t l = __LINE__ 487 ) 488 { 489 HipAssetLoadTask task; 490 taskName = taskName~":"~path; 491 492 task = loadBase(taskName, path, loadWorker(taskName, () 493 { 494 loadAsset(path, (void[] partialData) 495 { 496 task.givePartialData(partialData); 497 }, onFailureLoad(task)); 498 }, (_) 499 { 500 mainThreadLoadFunction(task.takePartialData(), onSuccessLoad(task)); 501 }, true), f, l); 502 503 return task; 504 } 505 } 506 507 @ExportD static IHipAssetLoadTask loadFile(string filePath, string f = __FILE__, size_t l = __LINE__) 508 { 509 void delegate(string,void delegate(HipAsset), void delegate(string err = "")) assetLoadFunc = 510 (pathOrLocation,onSuccess, onFailure) 511 { 512 import hip.filesystem.hipfs; 513 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 514 { 515 HipFileAsset asset = new HipFileAsset(pathOrLocation); 516 asset.load(data); 517 onSuccess(asset); 518 }).addOnError((string err) 519 { 520 onFailure("Could not read file with err: " ~ err); 521 }); 522 }; 523 HipAssetLoadTask task = loadSimple("Load File ", filePath, assetLoadFunc, f, l); 524 workerPool.startWorking(); 525 return task; 526 } 527 528 529 @ExportD static IHipAssetLoadTask loadImage(string imagePath, string f = __FILE__, size_t l = __LINE__) 530 { 531 void delegate(string,void delegate(HipAsset), void delegate(string err = "")) assetLoadFunc = 532 (pathOrLocation,onSuccess, onFailure) 533 { 534 import hip.filesystem.hipfs; 535 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 536 { 537 new Image(pathOrLocation, cast(ubyte[])data, (IImage img){onSuccess(cast(HipAsset)img);}, (){onFailure();}); 538 }).addOnError((string err) 539 { 540 onFailure("Could not read file with err: " ~ err); 541 }); 542 }; 543 HipAssetLoadTask task = loadSimple("Load Image ", imagePath, assetLoadFunc, f, l); 544 workerPool.startWorking(); 545 return task; 546 } 547 548 /** 549 * This can be totally loaded on the other thread. loadSimple is enough 550 */ 551 @ExportD static IHipAssetLoadTask loadAudio(string audioPath, string f = __FILE__, size_t l = __LINE__) 552 { 553 hiplog("AssetManager: Loading Audio: ", audioPath); 554 void delegate(string, void delegate(HipAsset), void delegate(string err)) assetLoadFunc = 555 (pathOrLocation, onSuccess, onFailure) 556 { 557 import hip.filesystem.hipfs; 558 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 559 { 560 auto clip = new hip.assets.audioclip.HipAudioClip(); 561 clip.loadFromMemory(data, getEncodingFromName(pathOrLocation), HipAudioType.SFX, 562 (in ubyte[] newData) 563 { 564 hiplog("AssetManager: Audio: Loaded ", audioPath); 565 onSuccess(clip); 566 }, (){onFailure("Could not load HipAudioClip.");}); 567 568 }).addOnError((string err) 569 { 570 onFailure("Could not read file "~audioPath~" with error "~err); 571 }); 572 }; 573 HipAssetLoadTask task = loadSimple("Load AudioClip", audioPath, assetLoadFunc, f, l); 574 workerPool.startWorking(); 575 return task; 576 } 577 578 @ExportD static IHipAssetLoadTask loadTexture(string texturePath, string f = __FILE__, size_t l = __LINE__) 579 { 580 import hip.util.memory; 581 hiplog("AssetManager: Loading Texture: ", texturePath); 582 void delegate(string, void delegate(void[]), void delegate(string err = "")) assetLoadFunc = 583 (pathOrLocation, onFirstStepComplete, onFailure) 584 { 585 import hip.filesystem.hipfs; 586 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 587 { 588 new Image(pathOrLocation, cast(ubyte[])data, 589 (IImage img) 590 { 591 onFirstStepComplete(toHeapSlice(img)); 592 }, (){onFailure();}); 593 }).addOnError((string err) 594 { 595 ErrorHandler.showErrorMessage("Could not read file ", err); 596 }); 597 }; 598 599 void delegate(void[], void delegate(HipAsset)) onPartialDataLoaded = 600 (partialData, onSuccess) 601 { 602 Image img = cast(Image)(cast(IImage)partialData.ptr); 603 HipTexture ret = new HipTexture(img); 604 hiplog("AssetManager: Texture: Loaded ", texturePath, " ", ret.toHipString); 605 onSuccess(ret); 606 void* gcObjCopy = cast(void*)img; 607 freeGCMemory(gcObjCopy); 608 }; 609 HipAssetLoadTask task = loadComplex("Load Texture", texturePath, assetLoadFunc, onPartialDataLoaded, f, l); 610 workerPool.startWorking(); 611 return task; 612 } 613 614 615 @ExportD static IHipAssetLoadTask loadCSV(string path, string f = __FILE__, size_t l = __LINE__) 616 { 617 HipAssetLoadTask task = loadSimple("Load CSV", path, (pathOrLocation, onSuccess, onError) 618 { 619 import hip.filesystem.hipfs; 620 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 621 { 622 auto ret = new HipCSV(); 623 ret.loadFromMemory(cast(string)data); 624 onSuccess(ret); 625 }).addOnError((string err) 626 { 627 onError("Error reading file: "~ err); 628 }); 629 }, f, l); 630 workerPool.startWorking(); 631 return task; 632 } 633 @ExportD static IHipAssetLoadTask loadINI(string path, string f = __FILE__, size_t l = __LINE__) 634 { 635 HipAssetLoadTask task = loadSimple("Load INI", path, (pathOrLocation, onSuccess, onError) 636 { 637 import hip.filesystem.hipfs; 638 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 639 { 640 auto ret = new HipINI(); 641 ret.loadFromMemory(cast(string)data, pathOrLocation); 642 onSuccess(ret); 643 }).addOnError((string err) 644 { 645 onError("Error reading file: "~ err); 646 }); 647 }, f, l); 648 649 workerPool.startWorking(); 650 return task; 651 } 652 @ExportD static IHipAssetLoadTask loadJSONC(string path, string f = __FILE__, size_t l = __LINE__) 653 { 654 HipAssetLoadTask task = loadSimple("Load JSONC", path, (pathOrLocation, onSuccess, onError) 655 { 656 import hip.filesystem.hipfs; 657 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 658 { 659 auto ret = new HipJSONC(); 660 ret.loadFromMemory(cast(string)data); 661 onSuccess(ret); 662 }).addOnError((string err) 663 { 664 onError("Error reading file: "~ err); 665 }); 666 }, f, l); 667 668 workerPool.startWorking(); 669 return task; 670 } 671 672 @ExportD static IHipAssetLoadTask loadTextureAtlas(string atlasPath, string texturePath = ":IGNORE", 673 string f = __FILE__, size_t l = __LINE__) 674 { 675 import hip.util.memory; 676 import hip.assets.textureatlas; 677 class TextureAtlasIntermediaryData 678 { 679 Image image; 680 HipTextureAtlas atlas; 681 } 682 void delegate(string, void delegate(void[]), void delegate(string err = "")) assetLoadFunc = 683 (pathOrLocation, onFirstStepComplete, onFailure) 684 { 685 import hip.filesystem.hipfs; 686 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 687 { 688 TextureAtlasIntermediaryData inter = new TextureAtlasIntermediaryData(); 689 inter.atlas = HipTextureAtlas.readFromMemory(cast(ubyte[])data, atlasPath, texturePath); 690 691 HipFS.read(inter.atlas.getTexturePath()).addOnSuccess((in ubyte[] imgData) 692 { 693 inter.image = new Image(pathOrLocation, imgData, 694 (IImage _) 695 { 696 onFirstStepComplete (toHeapSlice(inter)); 697 }, (){onFailure("Failure trying to read image");}); 698 }).addOnError((err){onFailure("Failure trying to read atlas");}); 699 }).addOnError((string err) 700 { 701 ErrorHandler.showErrorMessage("Could not read file: ", err); 702 }); 703 }; 704 705 void delegate(void[], void delegate(HipAsset)) onPartialDataLoaded = 706 (partialData, onSuccess) 707 { 708 scope(exit) freeGCMemory(partialData); 709 auto inter = cast(TextureAtlasIntermediaryData)partialData.ptr; 710 if(!inter.atlas.loadTexture(inter.image)) 711 { 712 assert(false, "Need to implement onError for texture atlas."); 713 } 714 onSuccess(inter.atlas); 715 freeGCMemory(partialData); 716 }; 717 HipAssetLoadTask task = loadComplex("Load TextureAtlas", atlasPath, assetLoadFunc, onPartialDataLoaded, f, l); 718 719 workerPool.startWorking(); 720 return task; 721 } 722 723 @ExportD static IHipAssetLoadTask loadTilemap(string tilemapPath, string f = __FILE__, size_t l = __LINE__) 724 { 725 import hip.util.memory; 726 import hip.assets.tilemap; 727 728 HipAssetLoadTask task = loadComplex("Load Tilemap ", tilemapPath, (pathOrLocation, onSuccess, onFailure) 729 { 730 import hip.filesystem.hipfs; 731 HipTilemap map; 732 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 733 { 734 map = HipTilemap.readTiledJSON(pathOrLocation, cast(ubyte[])data, (_) 735 { 736 map.loadImages(() 737 { 738 onSuccess(toHeapSlice(map)); 739 }, (){onFailure();}); 740 }, (){onFailure();}); 741 }).addOnError((string err) 742 { 743 onFailure("Failed loading tilemap data."~err); 744 }); 745 }, (partialData, onSuccess) 746 { 747 scope(exit) freeGCMemory(partialData); 748 auto map = cast(HipTilemap)partialData.ptr; 749 if(!map.loadTextures()) 750 { 751 assert(false, "Could not load HipTilemap textures " ~ map.path); 752 } 753 onSuccess(map); 754 }, f, l); 755 workerPool.startWorking(); 756 return task; 757 } 758 759 760 @ExportD static IHipAssetLoadTask loadTileset(string tilesetPath, string f = __FILE__, size_t l = __LINE__) 761 { 762 import hip.util.memory; 763 import hip.assets.tilemap; 764 HipAssetLoadTask task = loadComplex("Load Tileset ", tilesetPath, (pathOrLocation, onSuccess, onFailure) 765 { 766 import hip.filesystem.hipfs; 767 768 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 769 { 770 auto onTilesetJsonLoaded = delegate(HipTilesetImpl tileset) 771 { 772 tileset.loadImage((IImage _) 773 { 774 onSuccess(toHeapSlice(tileset)); 775 }, (){onFailure("Failed loading image for tileset");}); 776 }; 777 auto onTilesetJsonFailure = delegate(){onFailure("Failed loading tileset json");}; 778 HipTilesetImpl.readFromMemory(pathOrLocation, cast(string)data, onTilesetJsonLoaded, onTilesetJsonFailure); 779 }).addOnError((string err) 780 { 781 onFailure("Failed reading file for tileset"); 782 }); 783 }, (partialData, onSuccess) 784 { 785 scope(exit) freeGCMemory(partialData); 786 HipTilesetImpl tileset = cast(HipTilesetImpl)partialData.ptr; 787 if(!tileset.loadTexture()) 788 assert(false, "Could not load HipTileset texture " ~ tileset.path); 789 onSuccess(tileset); 790 }, f, l); 791 workerPool.startWorking(); 792 return task; 793 } 794 795 @ExportD static IHipTextureRegion createTextureRegion(IHipTexture texture, float u1 = 0.0, float v1 = 0.0, float u2 = 1.0, float v2 = 1.0) 796 { 797 return new HipTextureRegion(texture, u1, v1, u2, v2); 798 } 799 @ExportD static IHipTilemap createTilemap(uint width, uint height, uint tileWidth, uint tileHeight) 800 { 801 return new HipTilemap(width, height, tileWidth, tileHeight); 802 } 803 @ExportD static IHipTileset tilesetFromAtlas(IHipTextureAtlas atlas){return HipTilesetImpl.fromAtlas(cast(HipTextureAtlas)atlas);} 804 @ExportD static IHipTileset tilesetFromSpritesheet(Array2D_GC!IHipTextureRegion sp){return HipTilesetImpl.fromSpritesheet(sp);} 805 806 @ExportD static IHipAssetLoadTask loadFont(string fontPath, int fontSize = 48, string f = __FILE__, size_t l = __LINE__) 807 { 808 import hip.util.path; 809 hiplog("Trying to load the font ", fontPath, "EXT: ", fontPath.extension); 810 switch(fontPath.extension) 811 { 812 case "bmfont": 813 case "fnt": 814 return loadBMFont(fontPath, f, l); 815 case "ttf": 816 case "otf": 817 return loadTTF(fontPath, fontSize, f, l); 818 default: return null; 819 } 820 } 821 822 private static HipAssetLoadTask loadTTF(string ttfPath, int fontSize, string f = __FILE__, size_t l = __LINE__) 823 { 824 import hip.font.ttf; 825 import hip.assets.font; 826 import hip.util.memory; 827 828 class IntermediaryData 829 { 830 Hip_TTF_Font font; 831 ubyte[] rawImage; 832 this(Hip_TTF_Font fnt, ubyte[] img){font = fnt; rawImage = img;} 833 } 834 835 HipAssetLoadTask task = loadComplex("Load TTF", ttfPath, (pathOrLocation, onSuccess, onFailure) 836 { 837 import hip.filesystem.hipfs; 838 Hip_TTF_Font font = new Hip_TTF_Font(pathOrLocation, fontSize); 839 ubyte[] rawImage; 840 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] data) 841 { 842 if(!font.partialLoad(cast(ubyte[])data, rawImage)) 843 onFailure("Could not load font data"); 844 onSuccess(toHeapSlice(new IntermediaryData(font, rawImage))); 845 }).addOnError((string err) 846 { 847 onFailure("Could not read file "~err); 848 }); 849 }, (partialData, onSuccess) 850 { 851 scope(exit) freeGCMemory(partialData); 852 IntermediaryData i = (cast(IntermediaryData)partialData.ptr); 853 if(!i.font.loadTexture(i.rawImage)) 854 { 855 assert(false, "Failed loading TTF Font"); 856 } 857 HipFontAsset fnt = new HipFontAsset(i.font); 858 onSuccess(fnt); 859 }, f, l); 860 workerPool.startWorking(); 861 return task; 862 } 863 864 private static HipAssetLoadTask loadBMFont(string fontPath, string f = __FILE__, size_t l = __LINE__) 865 { 866 import hip.font.bmfont; 867 import hip.assets.font; 868 import hip.image; 869 import hip.util.memory; 870 871 class IntermediaryData 872 { 873 HipBitmapFont font; 874 HipImageImpl img; 875 this(HipBitmapFont fnt, HipImageImpl img){font = fnt; this.img = img;} 876 } 877 hiplog("Loading bmfont"); 878 879 HipAssetLoadTask task = loadComplex("Load BMFont", fontPath, 880 (pathOrLocation, onSuccess, onFailure) 881 { 882 import hip.filesystem.hipfs; 883 HipFS.read(pathOrLocation).addOnSuccess((in ubyte[] fontData) 884 { 885 HipBitmapFont font = new HipBitmapFont(); 886 if(!font.loadAtlas(cast(string)fontData, pathOrLocation)) 887 return onFailure("Could not load font atlas."); 888 HipImageImpl img = new HipImageImpl(font.getTexturePath); 889 890 HipFS.read(font.getTexturePath).addOnSuccess((in ubyte[] imgData) 891 { 892 img.loadFromMemory(cast(ubyte[])imgData, (IImage _) 893 { 894 onSuccess(toHeapSlice(new IntermediaryData(font, img))); 895 }, 896 () 897 { 898 onFailure("Could not decode image."); 899 }); 900 }).addOnError((string err) 901 { 902 onFailure("Could not load font image "~err); 903 }); 904 905 }).addOnError((string err) 906 { 907 onFailure("Could read file atlas"); 908 }); 909 }, 910 (partialData, onSuccess) 911 { 912 IntermediaryData i = (cast(IntermediaryData)partialData.ptr); 913 if(!i.font.loadTexture(new HipTexture(i.img))) 914 assert(false, "Could not read texture"); 915 HipFontAsset fnt = new HipFontAsset(i.font); 916 onSuccess(fnt); 917 freeGCMemory(partialData); 918 919 }, f, l); 920 workerPool.startWorking(); 921 return task; 922 } 923 924 925 926 /** 927 * Synchronized function for putting it into the completed queue for preparing the finish handlers 928 */ 929 private static void putComplete(HipAssetLoadTask task) 930 { 931 completeMutex.lock(); 932 if(task.result == HipAssetResult.loaded) 933 { 934 assert((cast(HipAsset)task.asset) !is null, "Can't putComplete a null asset."); 935 assets[task.path] = cast(HipAsset)task.asset; 936 } 937 completeQueue~= task; 938 completeMutex.unlock(); 939 } 940 941 static void addOnCompleteHandler(IHipAssetLoadTask task, void delegate(IHipAsset) onComplete) 942 { 943 if(task.asset !is null) 944 onComplete(task.asset); 945 else 946 { 947 hiplog("Added a complete handler for ", (cast(HipAssetLoadTask)task).name); 948 completeHandlers[cast(HipAssetLoadTask)task]~= onComplete; 949 } 950 } 951 952 static void addOnLoadingFinish(void delegate() onFinish) 953 { 954 workerPool.addOnAllTasksFinished(onFinish); 955 } 956 957 /** 958 * This function is responsible for calling worker's onTaskFinish on the main thread if it has one. 959 * After that, it will execute any deferred task to the AssetManager, such as setting a HipSprite or HipFont asset. 960 */ 961 static void update() 962 { 963 completeMutex.lock(); 964 if(completeQueue.length) 965 { 966 foreach(task; completeQueue) 967 { 968 //Subject to a logger 969 hiplog(task.name, " executing handlers"); 970 if(auto handlers = task in completeHandlers) 971 { 972 foreach(handler; *handlers) 973 handler(task._asset); 974 handlers.length = 0; 975 } 976 completeHandlers.remove(task); 977 } 978 completeQueue.length = 0; 979 } 980 completeMutex.unlock(); 981 workerPool.pollFinished(); 982 } 983 984 /** 985 * Cleans everything up. Puts AssetManager in an invalid state 986 */ 987 static void dispose() 988 { 989 import hip.error.handler; 990 workerPool.dispose(); 991 foreach(HipAsset asset; assets.byValue) 992 asset.dispose(); 993 destroy(assets); 994 destroy(loadQueue); 995 destroy(completeMutex); 996 destroy(workerPool); 997 } 998 }